/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/


/* -------------------------------------------------------------------------- */

#include "nu_variant.h"
#include "nu_functions.h"
#include "nu_os_std.h"
#include "nu_builtin_help.h"
#include "nu_lang_defs.h"


#include <functional>
#include <string>
#include <cstdlib>


/* -------------------------------------------------------------------------- */

namespace nu
{


/* -------------------------------------------------------------------------- */

#define THIS_MODULE "file"
#define MD_NAMESPACE THIS_MODULE "."


/* -------------------------------------------------------------------------- */

static struct module_file_t : public module_t
{
   module_file_t()
   {
      add_module(this, THIS_MODULE);
   }

   virtual bool load() throw() override
   {
      auto & hmap = builtin_help_t::get_instance();

      hmap[ {lang_item_t::MODULE, THIS_MODULE}] =
      {
         "The " THIS_MODULE " module provides access to the file functions",
         THIS_MODULE" module"
      };

      functions_t & fmap = functions_t::get_instance();

      // TODO implement open

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "open"}] =
      {
         "Establishes input/output (I/O) to a file.\n\n"
         "'filename' is the name of the file to be opened\n"
         "'mode'     is a string that determines the initial\n"
         "           positioning within the file,\n"
         "           and the action to be taken if the file does not exist.\n\n"
         "Valid mode strings are the following:\n"
         "- \"r\" read: Open file for input operations. The file must exist.\n"
         "- \"w\" write: Create an empty file for output operations.\n"
         "  If a file with the same name already exists, its contents are discarded\n"
         "  and the file is treated as a new empty file.\n"
         "- \"a\" append: Open file for output at the end of a file.\n"
         "  Output operations always write data at the end of the file, expanding it.\n"
         "  Repositioning operations are ignored.\n"
         "  The file is created if it does not exist.\n"
         "- \"r+\" read/update: Open a file for update the file must exist.\n"
         "- \"w+\" write/update: Create an empty file and open it for update.\n"
         "  If a file with the same name already exists its contents are discarded\n"
         "  and the file is treated as a new empty file.\n"
         "- \"a+\" append/update. Open a file for update with all output \n"
         "  operations writing data at the end of the file.\n"
         "  Repositioning operations affects the next input operations,\n"
         "  but output operations move the position back to the end of file.\n"
         "  The file is created if it does not exist.\n"
         ,
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE " open("
         PROGLANG_TYPE_STRING " filename, " PROGLANG_TYPE_STRING " mode)"
      };

      auto functor_open = [](
                             rt_prog_ctx_t & ctx,
                             const std::string& name,
                             const nu::func_args_t & args)
      {
         std::vector< variant_t > vargs;
         functions_t::get_functor_vargs(
            ctx,
            name,
            args,
         { variant_t::type_t::STRING, variant_t::type_t::STRING },
         vargs);

         return ctx.file_tbl.create_fd(vargs[0].to_string(), vargs[1].to_string());
      };

      fmap[MD_NAMESPACE "open"] = functor_open;


      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "seek"}] =
      {
         "Moves the file pointer to a specified location\n"
         "position: specifies the number of bytes from initial position\n"
         "origin: specifies the initial position if is assumes one of the "
         "following values\n"
         "  0 - Current position\n"
         "  1 - End of file\n"
         "  2 - Beginning of file\n",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "seek("
         PROGLANG_TYPE_INTEGER " filenumber, "
         PROGLANG_TYPE_INTEGER " position, "
         PROGLANG_TYPE_INTEGER " origin)"
      };


      auto functor_flush = [](
                              rt_prog_ctx_t & ctx,
                              const std::string& name,
                              const nu::func_args_t & args)
      {
         std::vector< variant_t > vargs;
         functions_t::get_functor_vargs(
            ctx,
            name,
            args,
         { variant_t::type_t::INTEGER },
         vargs);

         return ctx.file_tbl.flush_fd(vargs[0].to_int()) ?
                nu::variant_t(0) : nu::variant_t(-1);
      };

      fmap[MD_NAMESPACE "flush"] = functor_flush;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "flush"}] =
      {
         "Shall cause any unwritten data for that file "
         "number to be written to the file\n",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "flush("
         PROGLANG_TYPE_INTEGER " filenumber)"
      };


      auto functor_close = [](
                              rt_prog_ctx_t & ctx,
                              const std::string& name,
                              const nu::func_args_t & args)
      {
         std::vector< variant_t > vargs;
         functions_t::get_functor_vargs(
            ctx, name, args, { variant_t::type_t::INTEGER }, vargs);

         return ctx.file_tbl.close_fd(vargs[0].to_int()) ?
                nu::variant_t(0) : nu::variant_t(-1);
      };

      fmap[MD_NAMESPACE "close"] = functor_close;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "close"}] =
      {
         "Terminates input/output to a file.\n"
         "\nSee also Open, Read, Write, Errno, Errno$",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "close("
         PROGLANG_TYPE_INTEGER" filenum)"
      };


      auto functor_eof = [](
                            rt_prog_ctx_t & ctx,
                            const std::string& name,
                            const nu::func_args_t & args)
      {
         std::vector< variant_t > vargs;
         functions_t::get_functor_vargs(
            ctx, name, args, { variant_t::type_t::INTEGER }, vargs);

         auto file = ctx.file_tbl.resolve_fd(vargs[0].to_int());

         if (!file)
         {
            ctx.set_errno(EINVAL);
            return nu::variant_t(-1);
         }

         return nu::variant_t(feof(file));
      };

      fmap[MD_NAMESPACE "eof"] = functor_eof;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "eof"}] =
      {
         "Checks the end-of-file indicator.\n"
         "If no error has occurred on filenum, "
         "EOF returns 1 if EOF reached else 0\n"
         "If filenum is invalid, EOF sets errno to 22 and returns -1."
         "Otherwise, it returns a nonzero value\n",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "eof("
         PROGLANG_TYPE_INTEGER " filenum)"
      };


      auto functor_ferror = [](
                               rt_prog_ctx_t & ctx,
                               const std::string& name,
                               const nu::func_args_t & args)
      {
         std::vector< variant_t > vargs;
         functions_t::get_functor_vargs(
            ctx, name, args, { variant_t::type_t::INTEGER }, vargs);

         auto file = ctx.file_tbl.resolve_fd(vargs[0].to_int());

         if (!file)
         {
            ctx.set_errno(EINVAL);
            return nu::variant_t(-1);
         }

         return nu::variant_t(ferror(file));
      };

      fmap[MD_NAMESPACE "error"] = functor_ferror;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "error"}] =
      {
         "Tests for an error on a file.\n"
         "If no error has occurred on file, FError returns 0.\n"
         "If filenum is invalid, sets errno to 22 and returns -1.\n"
         "Otherwise, it returns a nonzero value\n",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE"error("
         PROGLANG_TYPE_INTEGER " filenum)"
      };


      auto functor_ftell = [](
                              rt_prog_ctx_t & ctx,
                              const std::string& name,
                              const nu::func_args_t & args)
      {
         std::vector< variant_t > vargs;
         functions_t::get_functor_vargs(
            ctx, name, args, { variant_t::type_t::INTEGER }, vargs);

         auto file = ctx.file_tbl.resolve_fd(vargs[0].to_int());

         if (!file)
         {
            ctx.set_errno(EINVAL);
            return nu::variant_t(-1);
         }

         return nu::variant_t(int(ftell(file)));
      };

      fmap[MD_NAMESPACE "tell"] = functor_ftell;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "tell"}] =
      {
         "Returns the current value of the position indicator of the file\n"
         "If no error has occurred on file, returns the position\n"
         "If filenum is invalid, sets errno to 22 and returns -1.\n",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "tell("
         PROGLANG_TYPE_INTEGER " filenum)"
      };


      auto functor_fsize = [](
                              rt_prog_ctx_t & ctx,
                              const std::string& name,
                              const nu::func_args_t & args)
      {
         std::vector< variant_t > vargs;
         functions_t::get_functor_vargs(
            ctx, name, args, { variant_t::type_t::INTEGER }, vargs);

         auto file = ctx.file_tbl.resolve_fd(vargs[0].to_int());

         if (!file)
         {
            ctx.set_errno(EINVAL);
            return nu::variant_t(-1);
         }

         int cur_pos = ftell(file);

         if (cur_pos < 0 || fseek(file, 0, SEEK_END) < 0)
         {
            ctx.set_errno(EINVAL);
            return nu::variant_t(-1);
         }

         auto size = ftell(file);

         if (fseek(file, cur_pos, SEEK_SET) < 0)
         {
            ctx.set_errno(EINVAL);
            return nu::variant_t(-1);
         }

         return variant_t(int_t(size));
      };

      fmap[MD_NAMESPACE "size"] = functor_fsize;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "size"}] =
      {
         "Returns the current size in bytes of the file\n"
         "If no error has occurred on file, returns the size\n"
         "If filenum is invalid, sets errno to 22 and returns -1.\n",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "size("
         PROGLANG_TYPE_INTEGER " filenum)"
      };


      auto functor_pwd = [](
                            rt_prog_ctx_t & ctx,
                            const std::string& name,
                            const nu::func_args_t & args)
      {
         functions_t::check_arg_num(args, 0, name);

         auto wd = _os_get_working_dir();

         return variant_t(wd, variant_t::type_t::STRING);
      };

      fmap[MD_NAMESPACE "pwd"] = functor_pwd;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE  "pwd"}] =
      {
         "Retrieves the current working directory",
         PROGLANG_TYPE_STRING " " MD_NAMESPACE "pwd()"
      };


      struct _erase_file
      {
         int operator()(const std::string& var) throw()
         {
            return _os_erase_file(var.c_str());
         }
      };

      fmap[MD_NAMESPACE "erase"] = functor_int_string < _erase_file > ;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "erase"}] =
      {
         "Deletes file from disk",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "delete("
         PROGLANG_TYPE_STRING " filename)"
      };


      struct _erase_dir
      {
         int operator()(const std::string& var) throw()
         {
            return _os_erase_dir(var.c_str());
         }
      };

      fmap[MD_NAMESPACE "rmdir"] = functor_int_string < _erase_dir > ;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "rmdir"}] =
      {
         "Deletes directory from disk",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "rmdir("
         PROGLANG_TYPE_STRING " path)"
      };


      struct _make_dir
      {
         int operator()(const std::string& var) throw()
         {
            return _os_make_dir(var.c_str());
         }
      };

      fmap[MD_NAMESPACE "mkdir"] = functor_int_string < _make_dir > ;

      hmap[ {lang_item_t::FUNCTION, MD_NAMESPACE "mkdir"}] =
      {
         "Creates a new directory",
         PROGLANG_TYPE_INTEGER " " MD_NAMESPACE "mkdir("
         PROGLANG_TYPE_STRING " path)"
      };


      return true;
   }

}
module_instance;


/* -------------------------------------------------------------------------- */

} // namespace nu

